# -*- coding: utf-8 -*-
"""
    xfree.admin.models
    ~~~~~~~~~~~~~~~~~~~~~~~~~
    文件描述：该模块包含所有与管理相关的模型。
    作者：xfree
    邮箱：cy_83k@qq.com

"""

import logging
# 导入日志模块，用于记录程序运行过程中的信息

from xfree.extensions import db, cache
# 从xfree.extensions模块中导入数据库对象db和缓存对象cache

from xfree.utils.database import CRUDMixin
# 从xfree.utils.database模块中导入CRUDMixin，它可能包含了数据库操作的增删改查方法

from xfree.utils.forms import SettingValueType, generate_settings_form
# 从xfree.utils.forms模块中导入SettingValueType（设置值类型）和generate_settings_form（生成设置表单的函数）


logger = logging.getLogger(__name__)
# 获取名为当前模块名的日志记录器


class SettingsGroup(db.Model, CRUDMixin):
    """
    表示数据库中的一个表，该表存储了一些设置组。每个设置组都有一个键（key）、名称（name）、描述（description）和一组设置项（settings）。
    """
    # SettingsGroup类，继承自数据库模型基类db.Model和CRUDMixin
    __tablename__ = "settingsgroup"
    # 定义该模型对应的数据库表名

    key = db.Column(db.String(255), primary_key=True)
    # 定义列key，类型为长度255的字符串，作为主键
    name = db.Column(db.String(255), nullable=False)
    # 定义列name，类型为长度255的字符串，不允许为空
    description = db.Column(db.Text, nullable=False)
    # 定义列description，类型为文本类型，不允许为空
    settings = db.relationship(
        "Setting", lazy="dynamic", backref="group", cascade="all, delete-orphan"
    )
    # 定义与Setting模型的关系
    # lazy="dynamic"表示查询时延迟加载
    # backref="group"表示在Setting模型中可以通过group属性反向引用SettingsGroup
    # cascade="all, delete-orphan"表示当删除SettingsGroup时，相关的Setting会被全部删除，并且当Setting的外键指向的SettingsGroup被删除时，这些Setting会成为孤儿记录并被删除

    def __repr__(self):
        return "<{} {}>".format(self.__class__.__name__, self.key)
        # 定义该类对象的字符串表示形式，格式为<类名 key>


class Setting(db.Model, CRUDMixin):
    """
    表示数据库中的一个表，该表存储了一些设置项。每个设置项都有一个键（key）、值（value）、所属的设置组（settingsgroup）、名称（name）、描述（description）、值类型（value_type）和额外属性（extra）。
    """
    # Setting类，继承自数据库模型基类db.Model和CRUDMixin
    __tablename__ = "settings"
    # 定义该模型对应的数据库表名

    key = db.Column(db.String(255), primary_key=True)
    # 定义列key，类型为长度255的字符串，作为主键
    value = db.Column(db.PickleType, nullable=False)
    # 定义列value，类型为可序列化类型（PickleType），不允许为空
    settingsgroup = db.Column(
        db.String(255),
        db.ForeignKey("settingsgroup.key", ondelete="CASCADE"),
        nullable=False
    )
    # 定义列settingsgroup，类型为长度255的字符串
    # 它是一个外键，关联到SettingsGroup模型的key列，当SettingsGroup的记录被删除时，级联删除相关的Setting记录
    # 该列不允许为空

    # 以下是用于表单显示的属性
    # 设置项的名称（在表单中显示）
    name = db.Column(db.String(200), nullable=False)
    # 定义列name，类型为长度200的字符串，不允许为空
    # 设置项的描述（在表单中显示）
    description = db.Column(db.Text, nullable=False)
    # 定义列description，类型为文本类型，不允许为空
    # 可用的值类型：字符串、整数、浮点数、布尔值、单选、多选
    value_type = db.Column(db.Enum(SettingValueType), nullable=False)
    # 定义列value_type，类型为SettingValueType枚举类型，不允许为空
    # 额外属性，例如验证相关的内容（最小值、最大长度等），对于选择类型的字段，需要choices属性
    extra = db.Column(db.PickleType)
    # 定义列extra，类型为可序列化类型

    @classmethod
    def get_form(cls, group):
        """
        根据给定的SettingsGroup名称返回一个用于编辑设置的表单。

        :param group: SettingsGroup名称，用于获取该组内的设置项来生成表单。
        """
        return generate_settings_form(settings=group.settings)

    @classmethod
    def get_all(cls):
        return cls.query.all()
        # 查询并返回所有的Setting对象

    @classmethod
    def update(cls, settings):
        """
        更新缓存并将更改存储到数据库中。

        :param settings: 包含设置项的字典。
        """
        # 更新数据库
        for key, value in settings.items():
            setting = cls.query.filter(Setting.key == key.lower()).first()
            # 根据传入字典中的键（转换为小写）查找对应的Setting对象
            setting.value = value
            # 更新该对象的值
            db.session.add(setting)
            # 将更新后的对象添加到数据库会话
            db.session.commit()
            # 提交会话，使更改生效
        cls.invalidate_cache()
        # 使缓存失效

    @classmethod
    def get_settings(cls, from_group=None):
        """
        以字典形式返回所有设置项或特定组内的设置项。
        字典的键为设置项的key，值为设置项的value。

        :param from_group: 可选参数，若指定，则只返回该组内的设置项。
        """
        result = None
        if from_group is not None:
            result = from_group.settings
        else:
            result = cls.query.all()
        settings = {}
        for setting in result:
            settings[setting.key] = setting.value
        return settings

    @classmethod
    @cache.cached(key_prefix="settings")
    def as_dict(cls, from_group=None, upper=True):
        """
        以字典形式返回所有设置项或特定组内的设置项，该方法使用了缓存。
        如果要使缓存失效，只需执行self.invalidate_cache()。

        :param from_group: 可选参数，若指定，则只返回该组内的设置项作为字典。
        :param upper: 如果upper为True，字典的键将使用大写字母。默认值为False。
        """
        settings = {}
        result = None
        if from_group is not None:
            result = SettingsGroup.query.filter_by(key=from_group).first_or_404()
            result = result.settings
        else:
            result = cls.query.all()
        for setting in result:
            if upper:
                setting_key = setting.key.upper()
            else:
                setting_key = setting.key
            settings[setting_key] = setting.value
        return settings

    @classmethod
    def invalidate_cache(cls):
        """
        使该对象的缓存元数据失效。
        """
        cache.delete_memoized(cls.as_dict, cls)